/*
 * This program will attempt to decypt a caesar cipher. 
 * It uses a very simple algorithm:
 * In natural English e compromises 12.7% of letters used,
 * t is 9.25% and a is 8.2%.
 * This algorithm will try to find the most common letters
 * and assume those letters must be a, e, and t.
 */
/* https://inventwithpython.com/hacking/chapter7.html */
/* http://practicalcryptography.com/ciphers/caesar-cipher/ */
/* https://www.xarg.org/2010/05/cracking-a-caesar-cipher/ there's a 20 line algorithm here that works. */
/* https://en.wikipedia.org/wiki/Letter_frequency#Relative_frequencies_of_letters_in_the_English_language */
/* 
   a 8.167%     b 1.492%     c 2.782%     d 4.253%     e 12.702%     f 2.228%     g 2.015% 	
   h 6.094%     i 6.966%     j 0.153%     k 3.872%     l 4.025%      m 2.406%     n 6.749% 	
   o 7.507%     p 1.929%     q 0.095%     r 5.987%     s 6.327%      t 9.256%     u 2.758% 	
   v 0.978%     w 5.370%     x 0.150%     y 3.978%     z 0.074%

   There are also relative frequencies of the first letter in a word
   There are also the most common double letters:  LL EE SS OO TT FF RR NN PP CC
*/


/* TODO  This has some issues:

   - echo "GNU Emacs is free" | ./caesar -s 1 | ./decrypt 

   - echo "GNU Emacs is Free" | ./caesar -s 1 | ./decrypt 

   - echo "GNU Emacs is free software: you can redistribute it and/or modify   
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or   
   (at your option) any later version.                                 
                                                                    
   GNU Emacs is distributed in the hope that it will be useful,        
   but WITHOUT ANY WARRANTY; without even the implied warranty of      
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the       
   GNU General Public License for more details.                        
                                                                    
   You should have received a copy of the GNU General Public License   
   along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>. " | ./caesar -s 1 | ./decrypt
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>  //strtol, alloc
#include <ctype.h>   //tolower
#include <stdbool.h>
#include <assert.h>
#include <argp.h>

#define BUFFER_SIZE 128
/* I know it's 26, but 32 is more natural for C */
#define ALPHABET_LENGTH 26
/*
  e compromises 12.7% of natural language
  if the sample e is 11.7% or 13.7%, then that's E
*/
#define VARIANCE 1

/* This represents the relative frequency of each letter of the English Alphabet */
float weight [] =
  {
   //a      b      c      d      e       f      h
   8.167, 1.492, 2.782, 4.253, 12.702, 2.228, 2.015,
   //h      i      j      k      l       m      n
   6.094, 6.966, 0.153, 3.872, 4.025,  2.406, 6.749,
   //o      p      q      r      s       t      u
   7.507, 1.929, 0.095, 5.987, 6.327,  9.256, 2.758,
   //v      w      x      y      z
   0.978, 5.370, 0.150, 3.978, 0.074
  };
char fileName [128];
char original_buffer [BUFFER_SIZE * sizeof (char)];
char buffer_sans_space [BUFFER_SIZE * sizeof (char)];
/* this will store the frequencies of letters
   a b c d e f ...
   0 1 2 3 4 5 ... */
int letter_count [ALPHABET_LENGTH];

const char * argp_program_version = "0.1";
const char * argp_program_bug_address = "jbranso@fastmail.com";

error_t argp_err_exit_status;

static const struct argp_option options [] =
  {
   {"file" , 'f', "FILE", 0, "Output the decrypted file." },
   { 0 }
  };

//define an argp parse function
error_t argp_parser (int opt, char *arg, struct argp_state *state)
{
  extern char fileName [];
  extern short int shift;
  switch (opt)
    {
      // if this parser function is called on an option that it doesn't recognize, then don't do anything.
    default:
      return ARGP_ERR_UNKNOWN;
    case 'f':
      {
        memcpy (fileName, arg, strlen (arg));
        break;
      }
    }
  return 0;
}

/* a string containing the basic usage of this program. */
struct argp argp =
  {
   options, argp_parser, 0,
   "A simple program to decrypt a caesar cipher."
  };


void store_input_in_original_buffer (FILE * stream)
{
  extern char original_buffer [];
  char c;
  int i;
  /* change the || to a && and you get an infinite loop. Why? */
  for (i = 0; (i < BUFFER_SIZE) && ((c = getc(stream)) != EOF); i++)
    {
      /* printf("char is %c\n", c); */
      //if c is printable or \n, then store in buffer
      if (isprint (c) || isspace (c)) 
        {
          original_buffer[i] = c;
          /* printf("stored %c as buffer[%d] == %c\n", c, i, original_buffer[i]); */
        }
      else if (c == '\0')
        {
          break;
        }
      else
        {
          printf("%s\n", "You done screwed up AAaron\n");
          break;
          /* printf("%s\n", original_buffer); */
          /* printf("%c\n", c); */
          assert(0);
        }
    }
  original_buffer[i] = '\0';
  /* printf("original buffer:\n%s\n", original_buffer); */
}

void store_input_in_buffer_sans_space ()
{
  extern char original_buffer [];
  extern char buffer_sans_space [];
  char c;
  int i;
  int number_of_non_letters = 0;
  for (i = 0; (i < BUFFER_SIZE)
         && ((c = original_buffer[i]) != '\0'); i++)
    {
      if (isalpha (c)) //if letter, store in buffer
        {
          buffer_sans_space[i - number_of_non_letters] = tolower (c);
        }
      // if space, punctuation or digit, move on
      else if (isspace (c) || ispunct(c) || isdigit(c)) 
        {
          number_of_non_letters++;
          continue;
        }
      else if (c == '\0')
        {
          break;
        }
      else
        {
          printf("%s\n", "You done screwed up AAaron2\n");
          /* printf("%s\n", buffer_sans_space); */
          printf("%c\n", c);
          assert(0);
        }
    }
  buffer_sans_space[i] = '\0';
  /* printf("buffer_sans_space:\n%s\n", buffer_sans_space); */
}


void store_input_in_buffer (FILE * stream)
{
  store_input_in_original_buffer (stream);
  store_input_in_buffer_sans_space ();
}

/* convert ascii lowercase to a number
   0-25
*/
int ascii_to_number (int number)
{
  return number - 97;
}

int number_to_ascii (int number)
{
  return number + 97;
}

/*
  This will store the number of times letters from buffer appear.
  a   b  c
  count_letters [ALPHABET_LENGTH] = {19, 4, 2 ...}
  In the above, a was found 19 times, b was found 4 times and c
  was found 2 times.
  It assumes buffer is [a-z]+
*/
void store_max_occurances ()
{
  extern char buffer_sans_space [];
  /* this will store the occurrences of letters */
  extern int letter_count [];
  int n;
  /* loop through the buffer and count the occurrences of letters */
  for (int i = 0; i < BUFFER_SIZE && buffer_sans_space[i] != '\0'; i++)
    {
      n = ascii_to_number(buffer_sans_space[i]);
      /* printf("number is %d\n", n); */
      /* printf("char is %c\n", buffer_sans_space[i]); */
      letter_count[n] = letter_count[n] + 1;
    }
}

void print_max_occurances ()
{
  extern int letter_count [];
  char a = 'a';
  for (int i = 0; i < ALPHABET_LENGTH; i++)
    {
      printf ("%c: %d\n", a + i, letter_count[i]);
    }
}

int find_e ()
{
  extern int letter_count [];
  int max = 0;
  char letter;

  for (int i = 0; i < ALPHABET_LENGTH; i++)
    {
      /* printf("letter_count is %d\n", letter_count[i]); */
      if (letter_count[i] > max)
        {
          max = letter_count[i];
          letter = number_to_ascii (i); 
        }
    }
  /* printf ("find_e says e is %c\n", letter); */
  return letter;
}

/* return the "corrective" shift of the caesar cipher */
int discover_shift (char c)
{
  int original_shift = abs ((int)'e' - (int) c);
  /* printf("original shift is %d\n", original_shift); */
  return abs (original_shift - 26);
}

/* This does some simple validation checks.
 * If the user put in something like "aaaaaa",
 then this will say exit and print a nice error.
*/
void check_if_valid_input ()
{
  /* validation checks
   * make sure a != e != t
   * if so, then the user probably tried to decrypt
   * "aaa aaaa" on "jjjjjj lllllll"
   */
  int a, e, t;
  a = ascii_to_number ('a');
  e = ascii_to_number ('e');
  t = ascii_to_number ('t');
  if (letter_count[a] == letter_count[e] || letter_count[a] == letter_count[t]
      || letter_count[e] == letter_count[t])
    {
      printf ("This is probably not a caesar cipher.\n");
      printf("a: %d, e %d, t %d\n", letter_count[a], letter_count[e], letter_count[t]);
      exit (EXIT_FAILURE);
    }
}

/* TODO this needs to support capitol letters as well */
/* the logic to print the encrypted text. */
void print_shifted (char c, int shift)
{
  if (! isalpha (c)) //c is not a letter?, then print it
    {
      putchar(c);
    }
  /* if current char + shift will be greater than 122,
     we cannot just add the shift value. */
  else if (((int) c + shift) > 122)
    {
      int subn = 122 - (int) c;
      int newShift = shift - (subn + 1);
      c = (char) (97 + newShift);
      putchar (c);
    }
  else
    {
      putchar ((char) ((int) c + shift));
    }
}

void decrypt (FILE * stream)
{
  extern char original_buffer [];
  char c;
  store_input_in_buffer (stream);
  store_max_occurances (); 
  print_max_occurances ();
  char e = find_e();
  int shift = discover_shift (e);
  /* printf("corrective shift is %d\n", shift); */
  /* printf("e is %c\n", (char) e); */
  //check_if_valid_input();
  /* printf("%s\n", original_buffer); */
  /* print the original decrypted buffer */
  for (int i = 0; (i < BUFFER_SIZE) && ((c = original_buffer[i]) != '\0'); i++)
    {
      print_shifted (c, shift);
    }
  /* print the rest of the decrypted stream */
  while ((c = getc(stream)) != EOF)
    {
      print_shifted (c, shift);
    }
  /* putchar('\n'); */
}

int main (int argc, char **argv) {
  extern char fileName [];
  argp_parse (&argp, argc, argv, 0, 0, 0);
  FILE * stream;
  if ((stream = fopen (fileName, "r")) == NULL)
    {
      stream = stdin;
    }
  decrypt (stream);
  fclose (stream);
  return 0;
}
